package org.mihalovic.PES.data;


import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.mihalovic.PES.Core.Graph.Node;
import org.mihalovic.PES.Core.Graph.Edge;



/**
 * <code>SourceGraphReader</code> implementation using database as source.
 */
public class DataManager {
    
    private HashMap<String, List<Integer>> invIndex;
    private HashMap<String, String> domains;            //Domain ID -> domain name
    private HashMap<String, String> reverseDomains;     //Domain name -> domain ID
    
    private HashMap<Integer, Rating> ratings;
    private HashMap<Integer, User> users;


    
    /**
     * Read and save inverted index of domains mapped to 'usefull' ratings.
     */
    public void LoadInvertedIndex(File source) throws FileNotFoundException, IOException, Exception {
        Pattern domRatings = Pattern.compile("^(f[1-9]\\d*)::([\\d+,]*)$");
        invIndex = new HashMap<String, List<Integer>>();
        
        BufferedReader reader = new BufferedReader(new FileReader(source));
        String inputLine;
        while((inputLine = reader.readLine()) != null) {
            Matcher m = domRatings.matcher(inputLine);
            if(!m.find())
                throw new Exception("Error parsing domain ID from index file");
            String domID = m.group(1);
            
            String[] ratIDs = m.group(2).split(",");
            List<Integer> ratList = new ArrayList<Integer>();
            for(String id : ratIDs)
                ratList.add(Integer.parseInt(id));
            
            invIndex.put(domID, ratList);
        }
    }
    
    
    public void LoadUsers(File source) throws FileNotFoundException, IOException, Exception {

        users = new HashMap<Integer, User>(250000);
        
        FileInputStream stream = new FileInputStream(source);
        InputStreamReader streamReader = new InputStreamReader(stream, "UTF-8");
        
        BufferedReader reader = new BufferedReader(streamReader);

        String inputLine;
        while((inputLine = reader.readLine()) != null) {
            if(inputLine.trim().isEmpty())
                continue;
            
            String[] tokens = inputLine.split("\\t");
            Integer id = Integer.valueOf(tokens[0]);
            users.put(id, new User(id, tokens[1]));
        }
    }


    public void LoadRatings(File source) throws FileNotFoundException, IOException, Exception {

        ratings = new HashMap<Integer, Rating>(500000);
        
        BufferedReader reader = new BufferedReader(new FileReader(source));
        String inputLine;
        while((inputLine = reader.readLine()) != null) {
            if(inputLine.trim().isEmpty())
                continue;
            
            String[] tokens = inputLine.split("\\s");
            Integer id = Integer.valueOf(tokens[0]);
            Integer from = Integer.valueOf(tokens[1]);
            Integer to = Integer.valueOf(tokens[2]);
            int weight = Integer.valueOf(tokens[4]);
            ratings.put(id, new Rating(id, from, to, tokens[3], (float)weight));
        }
    }
    
    
    public void LoadDomains(File source) throws FileNotFoundException, IOException, Exception {
        Pattern domain = Pattern.compile("^(f[1-9]\\d*)::\"([^\"]{2,})\"$");
        domains = new HashMap<String, String>();
        reverseDomains = new HashMap<String, String>();

        FileInputStream stream = new FileInputStream(source);
        InputStreamReader streamReader = new InputStreamReader(stream, "UTF-8");
        
        BufferedReader reader = new BufferedReader(streamReader);
        
        
        String inputLine;
        while((inputLine = reader.readLine()) != null) {
            Matcher m = domain.matcher(inputLine);
            if(!m.find())
                throw new Exception("Error parsing domain ID/name pair in input file");             //TODO: what about the DBException?
            
            String domID = m.group(1);
            String domName = m.group(2);
            domains.put(domID, domName);
            reverseDomains.put(domName, domID);
        }
    }


    public Set<Node> GetQueryNodes(String userQuery) {
        HashSet<Node> result = new HashSet<Node>();
        if(userQuery==null || userQuery.isEmpty())
            return result;
        
        HashSet<String> domainIDs = new HashSet<String>();
        domainIDs.addAll(Arrays.asList(retrieveDomainIDs(userQuery)));
        
        Collection<Rating> tempRats = ratings.values();
        for(Rating rat : tempRats) {
            if(rat.getWeight() > 0.0 && domainIDs.contains(rat.getDomainId())) {
                User from = users.get(rat.getFromUserId());
                User to = users.get(rat.getToUserId());

                result.add(new Node(from.getUserId(), from.getName(), null));
                result.add(new Node(to.getUserId(), to.getName(), null));
            }                
        }
        
        return result;
    }


    public Collection<Edge> GetPositiveRatings(String userQuery) {

        HashMap<String, Edge> result = new HashMap<String, Edge>();
        if(userQuery==null || userQuery.isEmpty())
            return result.values();

        String[] domainIDs = retrieveDomainIDs(userQuery);
        HashSet<Integer> ratingsOfDomains = new HashSet<Integer>();
        for(String domID : domainIDs)
            ratingsOfDomains.addAll(invIndex.get(domID));

        for(Integer ratingId : ratingsOfDomains) {
            Rating rating = ratings.get(ratingId);          //NOTE: possible fault point! (will it get by int value or reference?)
            if(rating.getWeight() <= 0.0)
                continue;                           //Not interested in negative ratings

            User fromUser = users.get(rating.getFromUserId());
            User toUser = users.get(rating.getToUserId());
                        
            String key = fromUser.getUserId() + "_" + toUser.getUserId();       //If already gotten, increment weight
            if(result.containsKey(key)) {
                Edge e = result.get(key);
                e.setWeight(e.getWeight() + rating.getWeight());
            }
            else {
                Edge e = new Edge(new Node(fromUser.getUserId(), fromUser.getName(), null),
                                  new Node(toUser.getUserId(), toUser.getName(), null), rating.getWeight());
                result.put(key, e);
            }
        }

        return result.values();
    }
    


    /**
     * Gains all available domains with number of positive ratings for each
     * @return Collection of <code>Domain</code> objects
     */    
    public ArrayList<Domain> GetDomainsWithRatingCounts() {
        ArrayList<Domain> result = new ArrayList<Domain>();

        for(String id : domains.keySet()) {
            String name = domains.get(id);
            
            if(!invIndex.containsKey(id)) {         //Domain without any ratings
                System.err.println("Domain: id=" + id + "; name=" + name + ";  has no ratings and will be ignored.");
                continue;
            }
            int ratingsCount = invIndex.get(id).size();
            Domain d = new Domain(name, ratingsCount);
            
            result.add(d);
        }

        return result;
    }


    public int GetNodesCount() {
        return users.size();
    }

    
    /** Get Domain IDs from search query */
    private String[] retrieveDomainIDs(String userQuery) {
        List<String> ids = new ArrayList<String>();
        
        String[] domainNames = userQuery.split(" OR ");
        for(String name : domainNames)
            ids.add(reverseDomains.get(name.substring(1, name.length()-1)/*Get rid of bounding double-quotes*/));

        return ids.toArray(new String[1]);
    }
}
